home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
SGI Developer Toolbox 6.1
/
SGI Developer Toolbox 6.1 - Disc 4.iso
/
public
/
sgiCD
/
cdreader.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-08-01
|
15KB
|
524 lines
/*
* cdreader - Plays an audio CD through the SGI Indigo speaker.
* I wrote this to learn about the SGI CD audio library routines.
*
* Original Author: Patrick Wolfe (pwolfe@kai.com, uunet!kailand!pwolfe)
* You are free to use this source code/program as you wish, at your own
* risk. Don't blame me for any mistakes in the code (or anything else,
* for that matter).
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <cdaudio.h>
#include <signal.h>
#include <audio.h>
#include <getopt.h>
/* number of samples in each frame is datasize divided by 2, since there are two bytes per sample */
#define SAMPS_PER_FRAME (CDDA_DATASIZE / 2)
/* warn if the current volume is below this, or the user may think this program doesn't work! */
#define LOW_VOLUME 7
/* constants for identifying fields in the start_at and stop_at arrays */
#define POS_TRACK 0
#define POS_MINUTE 1
#define POS_SECOND 2
#define POS_FRAME 3
int read_size; /* the number of frames to read at one time (CDbestreadsize fills this in) */
int current_track = -1; /* number of track currently playing */
int stop_playing; /* flag to indicate we're done */
int last_mhi, last_mlo, last_shi, last_slo, last_fhi, last_flo;
ALport aport;
CDPLAYER *cd;
CDTRACKINFO info;
CDPARSER *cdp;
CDSTATUS status;
CDFRAME *cdbuf;
/* called for every frame - data is already byte swapped and de-emphasized */
void
cd_audio_callback (arg, type, data)
int arg;
CDDATATYPES type;
void *data;
{
ALwritesamps (aport, data, SAMPS_PER_FRAME);
}
/*
* This callback is only enabled when the user specified a position to stop at
* other than the default "end of a track".
*
* called for every frame only during the last track.
*/
void
cd_ptime_callback (arg, type, data)
int arg;
CDDATATYPES type;
struct cdtimecode *data;
{
if ((data->mhi == last_mhi) && (data->mlo == last_mlo) && (data->shi == last_shi)
&& (data->slo == last_slo) && (data->fhi == last_fhi) && (data->flo == last_flo)) {
stop_playing++;
printf ("stopping at track %d time %d%d:%d%d frame %d%d\n", current_track,
data->mhi, data->mlo, data->shi, data->slo, data->fhi, data->flo);
}
}
/* called only when the program (track) number changes */
void
cd_pnum_callback (stop_at, type, data)
int stop_at[4];
CDDATATYPES type;
struct cdprognum *data;
{
if (CDgettrackinfo(cd, data->value, &info) == 0) {
perror ("CDgettrackinfo (pnum callback) failed");
}
else {
current_track = data->value;
/* stop if we've reached the end of the last whole track to play */
if ((stop_at[POS_MINUTE] == -1) && (current_track > stop_at[POS_TRACK])) {
stop_playing++; /* stop playing now */
return;
}
/* set this callback only if we need to check last_min:sec:frame */
if ((stop_at[POS_MINUTE] != -1) && (current_track == stop_at[POS_TRACK])) {
last_mhi = stop_at[POS_MINUTE] / 10;
last_mlo = stop_at[POS_MINUTE] % 10;
last_shi = stop_at[POS_SECOND] / 10;
last_slo = stop_at[POS_SECOND] % 10;
last_fhi = stop_at[POS_FRAME] / 10;
last_flo = stop_at[POS_FRAME] % 10;
CDsetcallback (cdp, cd_ptime, cd_ptime_callback, 0);
}
printf ("Track %2d: Length %02d:%02d\n", current_track, info.total_min, info.total_sec);
}
}
/* called only when the index (sub-program division) number changes */
void
cd_index_callback (arg, type, data)
int arg;
CDDATATYPES type;
struct cdprognum *data;
{
/* the index value is really only interesting if there is more than one */
/* it might be nice to find out what timecode these happen at */
/* I've tried using CDgetstatus(), and it resets the CD drive to the beginning of the disk */
printf ("Track %2d: Index %2d\n", current_track, data->value);
/* FYI: Index zero is normally a 2-3 second pause between tracks - CDseek and CDseektrack skip over it */
/* Index one is normally the start of a song. A few CDs are reported to have more indexes in them, most don't */
}
/*
* play selected audio data
*/
void
play_tracks (start_at, stop_at)
int start_at[4];
int stop_at[4];
{
int ctr, frames, first_secs, last_secs, total_secs, start_secs, start_min, start_sec;
/* check the stop_at values (if they were specified) */
if (stop_at[POS_MINUTE] != -1) {
if (CDgettrackinfo(cd, stop_at[POS_TRACK], &info) == 0) {
perror ("CDgettrackinfo (play_track 2) failed");
return;
}
total_secs = (info.total_min * 60) + info.total_sec;
last_secs = (stop_at[POS_MINUTE] * 60) + stop_at[POS_SECOND];
if (last_secs > total_secs) {
printf ("track %d is only %02d:%02d:%02d long (cannot stop at %02d:%02d:%02d) !\n",
stop_at[POS_TRACK], info.total_min, info.total_sec, info.total_frame,
stop_at[POS_MINUTE], stop_at[POS_SECOND], stop_at[POS_FRAME]);
return;
}
/* setup some values to make comparisons in cd_ptime_callback() faster */
last_mhi = stop_at[POS_MINUTE] / 10;
last_mlo = stop_at[POS_MINUTE] % 10;
last_shi = stop_at[POS_SECOND] / 10;
last_slo = stop_at[POS_SECOND] % 10;
last_fhi = stop_at[POS_FRAME] / 10;
last_flo = stop_at[POS_FRAME] % 10;
}
/*
* if the user wants to start at the beginning of a track,
* use the quick way to seek there
*/
if (start_at[POS_MINUTE] == -1) {
if (CDseektrack(cd, start_at[POS_TRACK]) == -1) {
perror ("CDseektrack");
return;
}
}
else { /* user wants to start at a specific point */
if (CDgettrackinfo(cd, start_at[POS_TRACK], &info) == 0) {
perror ("CDgettrackinfo (play_track 1) failed");
return;
}
/* make sure the user didn't try to offset beyond the end of the song */
first_secs = (start_at[POS_MINUTE] * 60) + start_at[POS_SECOND];
total_secs = (info.total_min * 60) + info.total_sec;
if (first_secs > total_secs) {
printf ("track %d is only %02d:%02d:%02d long (cannot skip to %02d:%02d:%02d) !\n",
start_at[POS_TRACK], info.total_min, info.total_sec, info.total_frame,
start_at[POS_MINUTE], start_at[POS_SECOND], start_at[POS_FRAME]);
return;
}
/* compute absolute position to seek to (position at start of song plus offset that the user supplied */
start_secs = (info.start_min * 60) + info.start_sec + first_secs;
start_min = start_secs / 60;
start_sec = start_secs % 60;
printf ("Track %d starts at absolute time %02d:%02d:%02d, seeking to absolute time %02d:%02d:%02d\n",
start_at[POS_TRACK], info.start_min, info.start_sec, info.start_frame,
start_min, start_sec, start_at[POS_FRAME]);
if (CDseek(cd, start_min, start_sec, start_at[POS_FRAME]) == -1) {
perror ("CDseek");
exit (1);
}
}
current_track = start_at[POS_TRACK];
/* initialize parser structure */
CDresetparser(cdp);
/* define callback routines for CDparseframe() */
CDsetcallback (cdp, cd_audio, cd_audio_callback, 0); /* called for every frame */
CDsetcallback (cdp, cd_pnum, cd_pnum_callback, stop_at); /* called only when the program (track) number changes */
CDsetcallback (cdp, cd_index, cd_index_callback, 0); /* called only when the index (sub-program division) number changes */
stop_playing = 0;
/* play audio data until we have gone past the last track we want to play */
while (stop_playing == 0) {
frames = CDreadda (cd, &cdbuf[0], read_size);
if (frames < 1) {
break; /* EOF (or read error) */
}
/*
* SGI BUG! The manpage for CDreadda() says it returns the number
* of frames read. In reality, it returns the number of bytes read
* into the buffer. Divide by CDDA_BLOCKSIZE to get the number of frames.
*
* I'd prefer it to work like the manpage says, return the number of frames.
* When this bug is fixed, delete the next line (and this comment).
*/
frames = frames / CDDA_BLOCKSIZE;
for (ctr = 0; (ctr < frames) && (ctr < read_size) && (stop_playing == 0); ctr++) {
CDparseframe (cdp, &cdbuf[ctr]);
}
}
}
/* signal handler - display where we are, so we can continue later */
void
cleanup ()
{
if (CDgetstatus(cd, &status) == 0) {
perror ("CDgetstatus (cleanup) failed");
}
else {
printf ("interrupted! Continue by entering: cdreader -f %d:%d:%d:%d\n",
status.track, status.min, status.sec, status.frame);
}
/* free parser memory */
CDdeleteparser(cdp);
/* close CD player port */
CDclose(cd);
/* close audio port */
ALcloseport(aport);
exit (0);
}
/* interpret track[:minute[:second[:frame]]] argument for starting/ending times */
int
parse_timecode (timecode, arg)
int timecode[4];
char *arg;
{
int errors = 0, position = 0, ctr = 0;
char *ptr, field[4];
for (ptr = arg; ; ptr++) {
/* we'll take any of a colon, period or null to mark the end of the field
(null also marks end of the string - see below) */
if ((*ptr == ':') || (*ptr == '.') || (*ptr == '\0')) { /* we've reached the end of the field */
field[ctr] = '\0';
switch (position) {
case POS_TRACK:
timecode[POS_TRACK] = atoi(field);
/* we'll check this value later, after we find out which tracks exist */
break;
case POS_MINUTE:
timecode[POS_MINUTE] = atoi(field);
if (timecode[POS_MINUTE] > 59) {
errors++;
fprintf (stderr, "invalid minute %d (valid range is 0 - 59)\n", timecode[POS_MINUTE]);
}
break;
case POS_SECOND:
timecode[POS_SECOND] = atoi(field);
if (timecode[POS_SECOND] > 59) {
errors++;
fprintf (stderr, "invalid second %d (valid range is 0 - 59)\n", timecode[POS_SECOND]);
}
break;
case POS_FRAME:
timecode[POS_FRAME] = atoi(field);
if (timecode[POS_FRAME] > 74) {
errors++;
fprintf (stderr, "invalid frame number %d (valid range is 0 - 74)\n", timecode[POS_FRAME]);
}
break;
default:
errors++;
fprintf (stderr, "error in format of timecode - too many colons\n");
break;
}
if (*ptr == '\0') { /* eof of string - that was the last field */
break;
}
else {
/* reset for the next field */
position++;
ctr = 0;
}
}
else if (ctr > 3) {
errors++;
fprintf (stderr, "error in format of timecode - too many digits in field %d\n", position);
break;
}
else {
field[ctr] = *ptr;
ctr++;
}
}
return (errors);
}
main (argc, argv)
int argc;
char *argv[];
{
long pvbuf[6];
int c, ctr, errors = 0;
int start_at[4], stop_at[4];
int volume = -1;
fputs ("CDreader V1.0\n", stdout);
start_at[POS_TRACK] = start_at[POS_MINUTE] = -1;
start_at[POS_SECOND] = start_at[POS_FRAME] = 0;
stop_at[POS_TRACK] = stop_at[POS_MINUTE] = -1;
stop_at[POS_SECOND] = stop_at[POS_FRAME] = 0;
while ((c = getopt(argc, argv, "f:l:v:")) != -1) {
switch (c) {
case 'f': /* specify track, minute, second, frame to start at (everything except track # is optional) */
errors += parse_timecode (start_at, optarg);
if (errors == 0) {
printf ("will start at track %d", start_at[POS_TRACK]);
if (start_at[POS_MINUTE] != -1) {
printf (", time %02d:%02d, frame %d\n", start_at[POS_MINUTE],
start_at[POS_SECOND], start_at[POS_FRAME]);
}
fputc ('\n', stdout);
}
break;
case 'l': /* specify last track to play */
errors += parse_timecode (stop_at, optarg);
if (errors == 0) {
if (stop_at[POS_MINUTE] != -1) {
printf ("will stop at track %d, time %02d:%02d, frame %d\n",
stop_at[POS_TRACK], stop_at[POS_MINUTE],
stop_at[POS_SECOND], stop_at[POS_FRAME]);
}
else {
printf ("will stop after track %d\n", stop_at[POS_TRACK]);
}
fputc ('\n', stdout);
}
break;
case 'v': /* specify volume */
volume = atoi(optarg);
if ((volume < 0) || (volume > 255)) {
errors++;
fprintf (stderr, "invalid volume %d - valid range is 0-255\n", volume);
}
break;
default: /* anything else is an error */
errors++;
}
}
if (errors > 0) {
fprintf (stderr, "usage: %s [-f track[:min[:sec[.frame]]]] [-l track[:min[:sec[.frame]]]] [-v volume]\n", argv[0]);
exit (1);
}
/* should really trap everything, right? */
signal (SIGHUP, cleanup);
signal (SIGINT, cleanup);
signal (SIGQUIT, cleanup);
signal (SIGTERM, cleanup);
cd = CDopen(0, "r");
if (cd == NULL) {
perror ("CDopen failed");
exit (1);
}
/* display disk info */
if (CDgetstatus(cd, &status) == 0) {
perror ("CDgetstatus failed");
exit (1);
}
if (status.state != CD_READY) {
fprintf (stderr, "The CD player is not ready\n");
CDclose(cd);
exit (1);
}
printf ("CD player is Ready, %d total tracks, total time on disk = %02d:%02d\n",
status.last, status.total_min, status.total_sec);
/* by default start at first track */
if (start_at[POS_TRACK] == -1) {
start_at[POS_TRACK] = status.first;
}
else if (start_at[POS_TRACK] < status.first) {
printf ("cannot start at track %d - first valid track is %d (we'll use that instead)\n", start_at[POS_TRACK], status.first);
start_at[POS_TRACK] = status.first;
}
else if (start_at[POS_TRACK] > status.last) {
printf ("cannot start at track %d - last valid track is %d\n", start_at[POS_TRACK], status.last);
CDclose(cd);
exit (1);
}
/* by default play through last track */
if (stop_at[POS_TRACK] == -1) {
stop_at[POS_TRACK] = status.last;
}
else if (stop_at[POS_TRACK] < start_at[POS_TRACK]) {
printf ("you want to stop before you start?\n", stop_at[POS_TRACK], start_at[POS_TRACK]);
CDclose(cd);
exit (1);
}
else if (stop_at[POS_TRACK] < status.first) {
printf ("cannot stop after track %d - first valid track is %d\n", stop_at[POS_TRACK], status.first);
CDclose(cd);
exit (1);
}
else if (stop_at[POS_TRACK] > status.last) {
printf ("cannot stop after track %d - last valid track is %d (we'll use that instead)\n", start_at[POS_TRACK], status.last);
stop_at[POS_TRACK] = status.last;
}
/* initialize audio port */
aport = ALopenport ("cdreader", "w", (ALconfig) 0);
if (aport == (ALport) 0) {
fprintf (stderr, "Could not open audio port\n");
CDclose(cd);
exit (1);
}
/* set audio port output sampling rate */
pvbuf[0] = AL_OUTPUT_RATE;
pvbuf[1] = AL_RATE_44100;
ALsetparams (AL_DEFAULT_DEVICE, pvbuf, 2);
/* get the current volume */
pvbuf[0] = AL_LEFT_SPEAKER_GAIN;
pvbuf[2] = AL_RIGHT_SPEAKER_GAIN;
ALgetparams (AL_DEFAULT_DEVICE, pvbuf, 4);
/* set volume, if the user specified a value */
if (volume > -1) {
pvbuf[1] = pvbuf[3] = volume;
ALsetparams (AL_DEFAULT_DEVICE, pvbuf, 4);
}
/* check for very low volume */
if ((pvbuf[1] < LOW_VOLUME) || (pvbuf[3] < LOW_VOLUME)) {
fputs ("warning! Your volume is set awfully low\n", stdout);
}
/* create parser structure */
cdp = CDcreateparser();
if (cdp == NULL) {
perror ("CDcreateparser");
CDclose(cd);
ALcloseport(aport);
exit (1);
}
/* find the best size to read */
read_size = CDbestreadsize(cd);
/* allocate a buffer to read into */
cdbuf = (CDFRAME *) malloc (read_size * CDDA_BLOCKSIZE);
/* play all tracks selected */
play_tracks (start_at, stop_at);
/* free parser memory */
CDdeleteparser(cdp);
/* close CD player port */
CDclose(cd);
/* close audio port */
ALcloseport(aport);
exit (0);
}